package com.morningstar.common.logger.operate;


import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.threadlocal.NamedThreadLocal;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import com.morningstar.common.constants.QueueConstant;
import com.morningstar.common.result.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import static com.morningstar.common.constants.HeaderConstants.*;

/**
 * 操作日志切面
 */
@Slf4j
@Aspect
@Component
public class OperateAdvice {

    /**
     * 排除敏感属性字段
     */
    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new NamedThreadLocal<>("Cost Time");

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(controllerLog)")
    public void boBefore(JoinPoint joinPoint, OperateLog controllerLog) {
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, OperateLog controllerLog, Object jsonResult) {
        try {
            handleLog(joinPoint, controllerLog, jsonResult);
        } catch (Exception exception) {
            log.error("operate log {}", ExceptionUtils.getStackTrace(exception));
        } finally {
            TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 处理参数
     *
     * @param joinPoint
     * @param controllerLog
     * @param jsonResult
     * @throws Exception
     */
    protected void handleLog(final JoinPoint joinPoint, OperateLog controllerLog, Object jsonResult) throws Exception {
        Long userId = StpUtil.getLoginIdAsLong();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Result result = JSON.parseObject(JSON.toJSONString(jsonResult), Result.class);
        String requestIp = null;
        String browser = null;
        String os = null;
        String requestMethod = null;
        String uri = null;
        Map<String, String[]> map = null;
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (servletRequestAttributes != null) {
            HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
            requestIp = httpServletRequest.getHeader(REQUEST_IP);
            browser = httpServletRequest.getHeader(REQUEST_BROWSER);
            os = httpServletRequest.getHeader(REQUEST_OS);
            requestMethod = httpServletRequest.getMethod();
            uri = httpServletRequest.getRequestURI();
            map = httpServletRequest.getParameterMap();
        }
        OperateLogEntity operateLogEntity = OperateLogEntity.builder()
                .title(controllerLog.title())
                .businessType(controllerLog.businessType().ordinal())
                .method(className + "." + methodName + "()")
                .requestMethod(requestMethod)
                .operatorType(controllerLog.operatorType().ordinal())
                .operateId(userId)
                .operateUrl(uri)
                .operateIp(requestIp)
                .browser(browser)
                .os(os)
                .operateTime(LocalDateTime.now())
                .status(result.getCode())
                .resultMsg(result.getMsg())
                .costTime(System.currentTimeMillis() - TIME_THREAD_LOCAL.get())
                .build();
        // 请求参数
        if (controllerLog.isSaveRequestData()) {
            SimplePropertyPreFilter excludePropertyPreFilter = new SimplePropertyPreFilter();
            excludePropertyPreFilter.getExcludes().addAll(Arrays.asList(controllerLog.excludeParamNames()));
            if (CollUtil.isNotEmpty(map)) {
                String params = JSON.toJSONString(map, excludePropertyPreFilter);
                operateLogEntity.setOperateParam(params);
            } else {
                Object args = joinPoint.getArgs();
                if (args != null) {
                    String params = argsArrayToString(joinPoint.getArgs(), excludePropertyPreFilter);
                    operateLogEntity.setOperateParam(params);
                }
            }
        }
        // 响应参数
        if (controllerLog.isSaveResponseData()) {
            operateLogEntity.setResultData(result.getData());
        }
        rabbitTemplate.convertAndSend(QueueConstant.LOGGER_OPERATE_QUEUE, JSON.toJSONString(operateLogEntity));
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray, SimplePropertyPreFilter simplePropertyPreFilter) {
        if (paramsArray == null || paramsArray.length == 0) {
            return "";
        }
        StringBuilder params = new StringBuilder();
        for (Object obj : paramsArray) {
            if (obj == null || isFilterObject(obj)) {
                continue;
            }
            try {
                Object jsonObj = JSON.toJSONString(obj, simplePropertyPreFilter);
                params.append(jsonObj.toString()).append(" ");
            } catch (Exception ignored) {
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param obj 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object obj) {
        Class<?> clazz = obj.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) obj;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) obj;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return obj instanceof MultipartFile
                || obj instanceof HttpServletRequest
                || obj instanceof HttpServletResponse
                || obj instanceof BindingResult;
    }

}
